<?php

// +----------------------------------------------------------------------
// | KyxsCMS Renew Novel Extend
// +----------------------------------------------------------------------
// | Copyright (c) Tealun Du All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: Tealun Du
// +----------------------------------------------------------------------

namespace app\home\controller;

use app\common\controller\Base;
use think\Db;
use app\api\controller\Source;
use think\facade\Config;
class Renew extends Base
{
    private $dir = '';
    private $file = '';
    private $lockFile = '';
    private $count = 0;
    private $limit = 0;
    private $access = 0;
    private $error = 0;
    private $totalPage = 0;
    private $error_page = 0;
    /**
     * 自动更新小说
     * 需配合服务器自动任务进行全量更新，或手动触发全量更新、指定范围（数据页码）更新
     * 服务器自动更新请在[计划任务]中设置[任务类型]为[访问URL](*此为宝塔设置方法，其他控制面板请咨询服务器提供商，暂未提供其他指引，后期会完善手动触发功能)
     * URL地址 http://www.yourwebsite.com/Home/Renew/index?renew_key=yourRenewKey
     * @param string $inKey
     * @param int $startPage
     * @param int $page
     * @param int $limit
     * @param bool $type
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\ModelNotFoundException
     * @throws \think\exception\DbException
     */
    public function index($inKey='',$startPage = 1,$page = 0,$limit = 50,$type=false){

        $renewKey = Config::get('web.auto_renew_key');
        if(!$renewKey) exit('未设置更新密钥');

        $getKey = input('get.renew_key');
        $key = $inKey!=''?$inKey:$getKey;
        if(!$key || $key !== $renewKey) exit('没有刷新密钥或刷新密钥不正确');//判断有无传入刷新密钥，密钥是否正确

        $dir = './runtime/log/renew';
        if(!is_dir($dir)) mkdir($dir, 0755);
        $this->dir = $dir;
        //检测任务锁定
        $this->lockFile = $this->dir.'/processing.lock';
        if(is_file($this->lockFile)) exit('正在执行刷新，请稍后再试');

        $this->limit = $limit;


        /** @var string $string 开始记录日志*/

        $typeName = $type?'指定范围':'全量范围';
        $renewName = $getKey?'自动更新':'手动更新';
        $time = time();
        $this->file = $this->dir.'/record'.$time.'.txt';
        $string =  date('Y-m-d H:i:s',$time).'开始执行'.$typeName.$renewName.'任务'.PHP_EOL;

        //设定系统总数据量和总页码计数
        $this->count =Db::name('novel')
            ->where('serialize','=',0)//状态为连载的
            ->where('status','=',1)//状态为正常的
            ->count();
        $this->totalPage = ceil($this->count/$this->limit);

        //die();
        $string .= '----------------------------------------'.PHP_EOL;
        $string .= '系统可刷新'.$this->count.'条数据，共有'.ceil($this->count/$this->limit).'页,每页'.$this->limit.'条数据'.PHP_EOL;

        $string .= '----------------------------------------'.PHP_EOL;

        file_put_contents($this->file,$string); //写入日志头部

        /******** background process starts here ********/
        ignore_user_abort(true);//在关闭连接后，继续运行php脚本
        /******** background process ********/
        set_time_limit(0); //no time limit，不设置超时时间（根据实际情况使用）
        /******** Rest of your code starts here ********/

        //正常情况下，网络搜集到的PHP长时间运行程序都是设置上述内容，以上如果设置后仍然超时返回502，考虑服务器运行环境 可以加入如下 在FASTCGI模式下的一些设置 避免超时
        ini_set('memory_limit', '-1');// 避免内存不足

        // 该方法是FPM提供的方法，只能运行在FastCGI模式下，在CLI模式或者是模块模式等非FPM模式下的话，会报错的，需要加上以下代码才可以
        if (!function_exists("fastcgi_finish_request")) {
            function fastcgi_finish_request()  {
            }
        }
        fastcgi_finish_request();// 完成响应, 关闭连接

        $renewType = Config::get('web.auto_renew_type');
        if($getKey && $renewType){//设定了自动更新模式
            $start = Config::get('web.auto_renew_start');
            $end = Config::get('web.auto_renew_end');

            switch ($renewType){
                case 1:
                    $this->pageRenew($start,$end);//执行根据指定页码自动更新
                    break;
                case 2:
                    $this->novelIdRenew($start,$end);//执行根据指定ID范围自动更新
                    break;
                default:
            }

        }else{//未设置更新模式的自动更新以及手动更新会按照页码更新方式处理
            $this->pageRenew($startPage,$page);
        }

        file_put_contents($this->file,'----------------------------------------'.PHP_EOL,FILE_APPEND);//完成日志
        file_put_contents($this->file,'本次更新任务完成，共计更新'.$this->access.'本书'.PHP_EOL,FILE_APPEND);
        file_put_contents($this->file,'----------------------------------------'.PHP_EOL,FILE_APPEND);//完成日志

        /** 完成后写入更新成功数据 供前端查阅 */
        $finishTime = time();
        $dataFile = $dir.'/renew_data'.$time.'.php';
        $total_time = $finishTime - $time;
        $total_time = self::secondToWord($total_time);
        $data = [
            'total_page'=>$this->totalPage,
            'limit'=>$this->limit,
            'error_page'=>$this->error_page,
            'renew_time'=>$time,
            'finish_time'=>$finishTime,
            'total_time'=>$total_time,
            'total_num' =>$this->count,
            'renew_num'=>$this->access,
            'error_num'=>$this->error,
            'type_text'=>$type?'手动更新':'自动更新'
        ];
        file_put_contents($dataFile,serialize($data));

        //解除任务锁定
        unlink($this->lockFile);

        /** 释放变量 */
        unset($ids,$dataFile,$data,$type,$time,$finishTime,$page,$dir,$inKey,$lockFile);
        exit();
    }

    private function pageRenew($startPage=1,$page=0){
        $page = $page && $page <= $this->totalPage
            ? $page : $this->totalPage;//如果有传入结束页面（用于管理后台），赋值传入值，传入页面大于总页面或未传入则取全部页码最后页

        /** @var int $r 最后一页数据量*/
        $r = $this->count%$this->limit;
        //重新整理本次任务数据总量，根据开始页面和结束页面计算，最后一页数据不等于$this->limit时取最后一页数量
        $this->count = $r && $page == ceil($this->count/$this->limit)
            ? ($page - $startPage)*$this->limit+$r : ($page - $startPage +1)*$this->limit;
        $this->totalPage = $page - $startPage;
        $string = '----------------------------------------'.PHP_EOL;
        $string .= '本次任务尝试更新'.$this->count.'条数据'.PHP_EOL;
        $string .= '从第'.$startPage.'页到第'.$page.'页,每页'.$this->limit.'条数据'.PHP_EOL;
        $string .= '----------------------------------------'.PHP_EOL;
        file_put_contents($this->file,$string,FILE_APPEND); //写入日志头部

        /** @var int $p 定义循环页数变量 开始分页获取小说并遍历更新*/
        for($p=$startPage;$p<=$page;$p++){
            file_put_contents($this->file,'执行刷新页码'.$p.''.PHP_EOL,FILE_APPEND);

            $ids = $this->getIds(1,$p);

            if(!$ids) break;
            $this->renew($ids,$p,$startPage);
            file_put_contents($this->file,'------------------------------------'.PHP_EOL,FILE_APPEND);
            sleep(2);//每更新一页数据，暂停2秒
        }
    }
    private function novelIdRenew($start,$end){
        //重新整理数据总量和总页数

        $this->count =Db::name('novel')
            ->where('id','between',[$start,$end])
            ->where('serialize','=',0)//状态为连载的
            ->where('status','=',1)//状态为正常的
            ->count();

        $this->totalPage = (int)ceil($this->count/$this->limit);
        $string = '----------------------------------------'.PHP_EOL;
        $string .= '本次任务尝试更新'.$this->count.'条数据'.PHP_EOL;
        $string .= '从ID'.$start.'到ID'.$end.'每页'.$this->limit.'条数据'.PHP_EOL;
        $string .= '----------------------------------------'.PHP_EOL;
        file_put_contents($this->file,$string,FILE_APPEND); //写入日志头部
        for($p=1;$p<=$this->totalPage;$p++){
            file_put_contents($this->file,'执行刷新页码'.$p.''.PHP_EOL,FILE_APPEND);
            $ids = $this->getIds(2,$p,[$start,$end]);
            if(!$ids) break;
            $this->renew($ids,$p);
            file_put_contents($this->file,'------------------------------------'.PHP_EOL,FILE_APPEND);
            sleep(2);//每更新一页数据，暂停2秒
        }
    }

    private function getIds($type,$p,$arr = []){
        $Novel = Db::name('novel')
            ->where('serialize','=',0)//状态为连载的
            ->where('status','=',1);//状态为正常的
        switch ($type){
            case 2 :
                if(!$arr) return false;
                $Novel = $Novel->where('id','between',$arr);
                break;
            default:
                break;
        }
        $ids =$Novel->field('id,title')->page($p,$this->limit)->select();

        if(!$ids){
            file_put_contents($this->file,'ERROR 未获取到第'.$p.'页更新列表'.PHP_EOL,FILE_APPEND);
            file_put_contents($this->file,'------------------------------------'.PHP_EOL,FILE_APPEND);
            $this->error_page++;
            $this->error = $this->error+$this->limit;
            return false;
        }
        return $ids;
    }

    private function renew($ids,$p,$startPage=1){
        /** @var object $Source 调用狂雨系统小说刷新类*/
        $Source = new Source();

        foreach ($ids as $k => $v){//遍历分页内容更新
            //设置任务锁定并记录当前执行信息
            $re = $Source->index($v['id']);
            if($re){//有更新记录状态和详细数据
                file_put_contents($this->file,date('Y-m-d H:i:s',time()).' | 更新成功：['.$v['title'].']('.$v['id'].') √ ok.'.PHP_EOL,FILE_APPEND);
                $this->access++;
            }else{//无更新，仅记录状态
                file_put_contents($this->file,date('Y-m-d H:i:s',time()).' | 暂无新章节：['.$v['title'].']('.$v['id'].')'.PHP_EOL,FILE_APPEND);
            }
            $status = $re?'√ Ok 新章节更新成功':'-_-! 暂无新章节';
            file_put_contents($this->lockFile,serialize([
                'all_novel'=>$this->count,
                'now_novel'=>($p-$startPage)*$this->limit+($k+1),
                'novel'=>[
                    'id'=>$v['id'],
                    'title'=>$v['title'],
                    'status'=>$status
                ]]));

        }
    }

    public static function secondToWord($s=0){
        //计算分钟
        //算法：将秒数除以60，然后下舍入，既得到分钟数
        $m    =    floor($s/60);
        //计算秒
        //算法：取得秒%60的余数，既得到秒数
        $s    =    $s%60;
        //计算小时
        //算法一样
        $h=0;
        if($m >60){
            $h    =    floor($m/60);
            $m = $h%60;
        }
        $d=0;
        if($h>24){
            $d = floor($h/24);
            $h = $d%24;
        }
        //如果只有一位数，前面增加一个0
        $s = (strlen($s)==1)?'0'.$s:$s;
        $s = $s?$s.'秒':'';
        $m = $m?$m.'分':'';
        $d = $d?$d.'天':'';
        $h = $h?$h.'小时':'';
        return $d.$h.$m.$s;
    }
}
